package com.billdowney.service.impl.base;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.billdowney.dao.base.BaseJpaDao;
import com.billdowney.entity.user.UserLoginInfo;
import com.billdowney.service.interfaces.base.BaseService;
import com.billdowney.service.interfaces.base.SpecParam;
import com.billdowney.service.interfaces.base.SpecType;
import com.billdowney.service.util.user.UserUtil;
import com.billdowney.util.CommonUtil;
import com.billdowney.util.return_type.DataPaging;
import com.billdowney.util.return_type.PagingUtil;

/**
 * 基础业务实现类
 * 
 * @author 超级小富翁 - BillDowney
 * @date 2019年4月19日 上午11:22:21
 * @param <D> dao层接口
 * @param <T> entity类
 */
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public abstract class BaseServiceImpl<D extends BaseJpaDao<T, ID>, T, ID extends Serializable>
		implements BaseService<T> {

	@PersistenceContext
	private EntityManager entityManager;

	/**
	 * 一个用于内部使用dao层的方法
	 */
	public D dao;

	public abstract void setDao(D dao);

	@Override
	public UserLoginInfo getUserLoginInfo() {
		return UserUtil.getSecurityUser();
	}

	@Override
	public Specification<T> getSpec(SpecParam specParam) {
		// 组装查询类
		return new Specification<T>() {
			private static final long serialVersionUID = 1L;

			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
				// 条件
				List<Predicate> predicates = new ArrayList<>();
				// 排序
				List<Order> orders = new ArrayList<>();
				for (SpecType specType : specParam.getSpecTypes()) {
					switch (specType) {
					case equal:
						for (Entry<String, ?> entry : specParam.getEqual().entrySet()) {
							predicates.add(criteriaBuilder.equal(root.get(entry.getKey()), entry.getValue()));
						}
						break;
					case asc:
						for (String str : specParam.getAsc()) {
							orders.add(criteriaBuilder.asc(root.get(str)));
						}
						break;
					case desc:
						for (String str : specParam.getDesc()) {
							orders.add(criteriaBuilder.desc(root.get(str)));
						}
						break;
					case in:
						for (Entry<String, Set<Object>> entry : specParam.getIn().entrySet()) {
							predicates.add(criteriaBuilder.in(root.get(entry.getKey())).in(entry.getValue()));
						}
						break;
					case notIn:
						for (Entry<String, Set<Object>> entry : specParam.getNotIn().entrySet()) {
							predicates.add(criteriaBuilder.in(root.get(entry.getKey())).in(entry.getValue()).not());
						}
						break;
					case isEmpty:
						for (String str : specParam.getIsEmpty()) {
							predicates.add(criteriaBuilder.isEmpty(root.get(str)));
						}
						break;
					case isNotEmpty:
						for (String str : specParam.getIsNotEmpty()) {
							predicates.add(criteriaBuilder.isNotEmpty(root.get(str)));
						}
						break;
					case isNotNull:
						for (String str : specParam.getIsNotNull()) {
							predicates.add(criteriaBuilder.isNotNull(root.get(str)));
						}
						break;
					case isNull:
						for (String str : specParam.getIsNull()) {
							predicates.add(criteriaBuilder.isNull(root.get(str)));
						}
						break;
					case lessThan:
						for (Entry<String, ? extends Comparable<Object>> entry : specParam.getLessThan().entrySet()) {
							predicates.add(criteriaBuilder.lessThan(root.get(entry.getKey()), entry.getValue()));
						}
						break;
					case lessThanOrEqualTo:
						for (Entry<String, ? extends Comparable<Object>> entry : specParam.getLessThanOrEqualTo()
								.entrySet()) {
							predicates
									.add(criteriaBuilder.lessThanOrEqualTo(root.get(entry.getKey()), entry.getValue()));
						}
						break;
					case greaterThan:
						for (Entry<String, ? extends Comparable<Object>> entry : specParam.getGreaterThan()
								.entrySet()) {
							predicates.add(criteriaBuilder.greaterThan(root.get(entry.getKey()), entry.getValue()));
						}
						break;
					case greaterThanOrEqualTo:
						for (Entry<String, ? extends Comparable<Object>> entry : specParam.getGreaterThanOrEqualTo()
								.entrySet()) {
							predicates.add(
									criteriaBuilder.greaterThanOrEqualTo(root.get(entry.getKey()), entry.getValue()));
						}
						break;
					case like:
						for (Entry<String, String> entry : specParam.getLike().entrySet()) {
							predicates.add(criteriaBuilder.like(root.get(entry.getKey()), entry.getValue()));
						}
						break;
					case notEqual:
						for (Entry<String, ?> entry : specParam.getNotEqual().entrySet()) {
							predicates.add(criteriaBuilder.notEqual(root.get(entry.getKey()), entry.getValue()));
						}
						break;
					case notLike:
						for (Entry<String, String> entry : specParam.getNotLike().entrySet()) {
							predicates.add(criteriaBuilder.notLike(root.get(entry.getKey()), entry.getValue()));
						}
						break;
					}
				}
				if (CommonUtil.isNotEmpty(predicates)) {
					query.where(criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()])));
				}
				if (CommonUtil.isNotEmpty(orders)) {
					query.orderBy(orders);
				}
				return query.getRestriction();
			}
		};
	}

	@Override
	public DataPaging findAll(Specification<T> spec, Integer pageNum, Integer pageSize) {
		return PagingUtil.getDataPaging(dao.findAll(spec, PagingUtil.getPageable(pageNum, pageSize)));
	}

	@Override
	public DataPaging findAll(SpecParam specParam, Integer pageNum, Integer pageSize) {
		return this.findAll(this.getSpec(specParam), pageNum, pageSize);
	}

	@Override
	public DataPaging findAll(Integer pageNum, Integer pageSize) {
		return PagingUtil.getDataPaging(dao.findAll(PagingUtil.getPageable(pageNum, pageSize)));
	}

	@Override
	public List<T> findAll(SpecParam specParam) {
		return dao.findAll(this.getSpec(specParam));
	}

}